package utils

import (
	"errors"
	"fmt"
	"net"
	"os/exec"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/exp/slices"
)

var (
	spaceTwo   = regexp.MustCompile(" {2,}")
	matchVia   = regexp.MustCompile(`via *([0-9]+\.){3}([0-9]+)`)
	matchDev   = regexp.MustCompile(`dev *([0-9a-zA-Z]+)`)
	matchSrc   = regexp.MustCompile(`src *([0-9]+\.){3}([0-9]+)`)
	matchFirst = regexp.MustCompile(`^([0-9]+\.){3}([0-9]+)(/[0-9]+)?`)

	maskMap = map[string]byte{
		"FF": 8,
		"FE": 7,
		"FC": 6,
		"FB": 5,
		"F0": 4,
		"E0": 3,
		"C0": 2,
		"80": 1,
		"00": 0,
	}

	RouteTab, _ = ParseRouteTable()
)

func ParseRouteTable() (RouteTable, error) {
	switch runtime.GOOS {
	case "darwin":
		return ParseDarwinRouteTable()
	case "linux":
		return ParseLinuxRouteTable()
	case "windows":
		return ParseWindowsRouteTable("")
	default:
		return RouteTable{}, fmt.Errorf("unsupported platform")
	}
}

func GetIPFrom(dst net.IP) net.IP {
	ri := RouteTab.Match(dst)
	if ri == nil {
		return nil
	}
	return ri.From
}

// RouteItem 路由表中的一项
type RouteItem struct {
	To      net.IPNet
	From    net.IP
	NextHop net.IP
	Device  string // 网卡名或IP地址字符串
}

func (r RouteItem) String() string {
	return fmt.Sprintf("to %s from %s dev %s next %s", r.To, r.From, r.Device, r.NextHop)
}

func (r RouteItem) MaskLen() int {
	a, _ := r.To.Mask.Size()
	return a
}

// RouteTable 路由表
type RouteTable []RouteItem

// Sort 将路由表中的路由项按照子网掩码长度由高到低排列
func (r RouteTable) Sort() {
	sort.Slice(r, func(i, j int) bool {
		return r[i].MaskLen() > r[j].MaskLen()
	})
}

// Match 匹配路由项
func (r RouteTable) Match(dst net.IP) *RouteItem {
	for _, i := range r {
		if i.To.Contains(dst) {
			return &i
		}
	}
	return nil
}

// ParseIpPort 解析ip和端口
func ParseIpPort(addr string) (net.IP, uint16, error) {
	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		return nil, 0, err
	}
	ip := net.ParseIP(host)
	portNum, err := strconv.Atoi(port)
	return ip, uint16(portNum), err
}

// ParseNetmaskStr 解析格式为255.255.254.0的子网掩码字符串为子网掩码长度，返回-1表示字符串非法
func ParseNetmaskStr(str string) int {
	allowI := []byte{0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00}
	lenI := []byte{8, 7, 6, 5, 4, 3, 2, 1, 0}
	sl := strings.Split(str, ".")
	if len(sl) != 4 {
		return -1
	}
	final := false
	result := 0
	for _, value := range sl {
		num, err := strconv.Atoi(value)
		if err != nil || num > 255 || num < 0 {
			return -1
		}
		ii := slices.Index(allowI, byte(num))
		if ii == -1 {
			return -1
		}
		result += int(lenI[ii])
		if final {
			if lenI[ii] != 0 {
				return -1
			}
		} else {
			if lenI[ii] != 8 {
				final = true
			}
		}
	}
	return result
}

// ParseNetmaskHex 解析格式为0xfffe0000的子网掩码字符串并返回子网掩码长度，如果非法就返回-1
func ParseNetmaskHex(str string) int {
	str, _ = strings.CutPrefix(str, "0x")
	str = strings.ToUpper(str)
	if len(str) != 8 {
		return -1
	}
	end := false
	maskLen := 0
	for i := 0; i < len(str); i += 2 {
		l, ok := maskMap[str[i:i+2]]
		if !ok {
			return -1
		}
		if end && l != 0 {
			return -1
		}
		if l != 8 {
			end = true
		}
		maskLen += int(l)
	}
	return maskLen
}

// ParseWindowsRouteTable 解析Windows系统的路由表，参数为route print -4的输出结果，可以为空字符串
func ParseWindowsRouteTable(input string) (RouteTable, error) {
	if input == "" {
		cmd := exec.Command("cmd", "/C", "route print -4")
		output, err := cmd.CombinedOutput()
		if err != nil {
			return nil, err
		}
		input = string(output)
	}
	// 过滤掉没有用的行
	filter := regexp.MustCompile(`^ *([0-9]+.){3}.*[0-9]$`)
	rt := RouteTable{}
	for _, v := range strings.Split(input, "\n") {
		if filter.MatchString(v) {

			items := strings.Split(spaceTwo.ReplaceAllString(strings.TrimSpace(v), " "), " ")
			ri := RouteItem{}
			mask1 := ParseNetmaskStr(items[1])
			_, n, e := net.ParseCIDR(fmt.Sprintf("%s/%d", items[0], mask1))
			if e != nil {
				return nil, e
			}
			ri.To = *n
			ri.From = net.ParseIP(items[3])
			if net.ParseIP(items[2]) == nil {
				ri.NextHop = net.ParseIP(items[3])
			} else {
				ri.NextHop = net.ParseIP(items[2])
			}
			ri.Device = ri.NextHop.To4().String()
			rt = append(rt, ri)
		}
	}
	rt.Sort()
	return rt, nil
}

// ParseDarwinRouteTable 解析macos系统的路由表
func ParseDarwinRouteTable() (RouteTable, error) {
	cmd := exec.Command(Shell, "-c", "netstat -nr -f inet | grep -v 'link' | sed 's|^default|0.0.0.0/0|g' | grep '^[0-9]' | awk '{print $1,$2,$4}'")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, err
	}
	out := strings.Split(string(output), "\n")
	outList := make([][]string, 0)
	devToIp := make(map[string]string)
	cmd = exec.Command(Shell, "-c", "ifconfig | grep '^[a-zA-Z0-9].*[0-9]$'  | awk -F ':' '{print $1}'")
	output, err = cmd.CombinedOutput()
	if err != nil {
		return nil, err
	}
	ifs := strings.Split(string(output), "\n")
	if len(ifs) == 0 {
		return nil, errors.New("not network interface has been find")
	}
	for _, line := range out {
		strs := strings.Split(line, " ")
		if len(strs) < 3 {
			continue
		}
		if net.ParseIP(strs[1]).To4() == nil && !slices.Contains(ifs, strs[1]) {
			continue
		}
		outList = append(outList, strs)
		devToIp[strs[2]] = getIpOfIFByIfconfig(strs[2])
	}
	rt := RouteTable{}
	for _, v := range outList {
		ri := RouteItem{}
		ri.Device = v[2]
		if ip := net.ParseIP(v[1]); ip != nil {
			ri.NextHop = ip
		} else {
			ri.NextHop = net.ParseIP(devToIp[v[1]])
		}
		ri.To = *(parseDarwinNet(v[0]))
		ri.From = net.ParseIP(devToIp[v[2]])
		rt = append(rt, ri)
	}
	rt.Sort()
	return rt, nil
}

// parseDarwinNet 解析macos netstat -nr 命令输出的第一个字段
func parseDarwinNet(str string) *net.IPNet {
	dotNum := strings.Count(str, ".")
	if strings.Contains(str, "/") {
		strs := strings.Split(str, "/")
		finalNet := ""
		switch dotNum {
		case 0:
			finalNet = fmt.Sprintf("%s.0.0.0/%s", strs[0], strs[1])
		case 1:
			finalNet = fmt.Sprintf("%s.0.0/%s", strs[0], strs[1])
		case 2:
			finalNet = fmt.Sprintf("%s.0/%s", strs[0], strs[1])
		case 3:
			finalNet = str
		}
		_, b, err := net.ParseCIDR(finalNet)
		if err != nil {
			return nil
		}
		return b
	}
	finalNet := ""
	switch dotNum {
	case 0:
		finalNet = fmt.Sprintf("%s.0.0.0/8", str)
	case 1:
		finalNet = fmt.Sprintf("%s.0.0/16", str)
	case 2:
		finalNet = fmt.Sprintf("%s.0/24", str)
	case 3:
		finalNet = fmt.Sprintf("%s/32", str)
	}
	_, b, err := net.ParseCIDR(finalNet)
	if err != nil {
		return nil
	}
	return b
}

// getIpOfIFByIfconfig 根据网卡名获取网卡IP地址，利用ifconfig命令
func getIpOfIFByIfconfig(dev string) string {
	cmd := exec.Command(Shell, "-c", fmt.Sprintf("ifconfig %s | grep 'inet ' | awk '{print $2}'", dev))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return ""
	}
	ips := strings.Split(string(output), "\n")
	return ips[0]
}

// getIpOfIFByIp 根据网卡名获取网卡IP地址，利用ip命令
func getIpOfIFByIp(dev string) string {
	cmd := exec.Command(Shell, "-c", fmt.Sprintf("ip addr show %s | grep 'inet ' | awk '{print $2}' | sed 's|/.*||g'", dev))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return ""
	}
	ips := strings.Split(string(output), "\n")
	return ips[0]
}

// ParseLinuxRouteTable 解析Linux系统路由表
func ParseLinuxRouteTable() (RouteTable, error) {
	rt := RouteTable{}
	if CmdProbe("ip") {
		cmd := exec.Command(Shell, "-c", "ip route | sed 's|^default|0.0.0.0/0|'")
		output, err := cmd.CombinedOutput()
		if err != nil {
			return nil, err
		}
		out := strings.Split(string(output), "\n")
		for _, line := range out {
			defRoute := false
			ri := RouteItem{}
			v := matchVia.FindAllString(line, 1)
			if len(v) > 0 {
				ss := strings.Split(v[0], " ")
				if len(ss) != 2 {
					continue
				}
				ri.NextHop = net.ParseIP(ss[1])
				defRoute = true
			}
			d := matchDev.FindAllString(line, 1)
			if len(d) > 0 {
				ss := strings.Split(d[0], " ")
				if len(ss) != 2 {
					continue
				}
				ri.Device = ss[1]
			} else {
				continue
			}
			s := matchSrc.FindAllString(line, 1)
			if len(s) > 0 {
				ss := strings.Split(s[0], " ")
				if len(ss) != 2 {
					continue
				}
				ri.From = net.ParseIP(ss[1])
				if !defRoute {
					ri.NextHop = net.ParseIP(ss[1])
				}
			} else {
				ri.From = net.ParseIP(getIpOfIFByIp(ri.Device))
			}
			f := matchFirst.FindAllString(line, 1)
			if len(f) > 0 {
				ss := strings.Split(f[0], " ")
				if len(ss) != 1 {
					continue
				}
				ri.To = *(parseDarwinNet(ss[0]))
			} else {
				continue
			}
			rt = append(rt, ri)
		}
		rt.Sort()
		cmd = exec.Command(Shell, "-c", "ip addr show lo | grep 'inet ' | awk '{print $2}'")
		output, err = cmd.CombinedOutput()
		if err != nil {
			return rt, nil
		}
		ri := RouteItem{
			Device:  "lo",
			From:    net.IPv4(127, 0, 0, 1).To4(),
			NextHop: net.IPv4(127, 0, 0, 1).To4(),
		}
		ss := strings.Split(string(output), "\n")
		if len(ss) == 0 {
			return rt, nil
		}
		_, n, err := net.ParseCIDR(ss[0])
		if err != nil {
			return rt, nil
		}
		ri.To = *(n)
		rt = append(rt, ri)
		rt.Sort()
		return rt, nil
	}
	if CmdProbe("route") && CmdProbe("ifconfig") {
		cmd := exec.Command(Shell, "-c", "route -nv | grep '^[0-9]' | awk '{print $1,$2,$3,$8}'")
		output, err := cmd.CombinedOutput()
		if err != nil {
			return nil, err
		}
		out := strings.Split(string(output), "\n")
		for _, line := range out {
			defRoute := false
			ss := strings.Split(line, " ")
			if len(ss) != 4 {
				continue
			}
			ri := RouteItem{
				Device: ss[3],
			}
			mask := ParseNetmaskStr(ss[2])
			if mask == -1 {
				continue
			}
			toStr := fmt.Sprintf("%s/%d", ss[0], mask)
			if toStr == "0.0.0.0/0" {
				defRoute = true
			}
			_, n, err := net.ParseCIDR(toStr)
			if err != nil {
				continue
			}
			ri.To = *n
			from := getIpOfIFByIfconfig(ri.Device)
			ri.From = net.ParseIP(from).To4()
			if ri.From == nil {
				continue
			}
			if defRoute {
				ri.NextHop = net.ParseIP(ss[1]).To4()
				if ri.NextHop == nil {
					continue
				}
			}
			if ss[1] == "0.0.0.0" {
				ri.NextHop = net.ParseIP(from).To4()
			}
			rt = append(rt, ri)
		}
		rt.Sort()
		cmd = exec.Command(Shell, "-c", "ifconfig lo | grep 'inet ' | awk '{print $2,$4}'")
		output, err = cmd.CombinedOutput()
		if err != nil {
			return rt, nil
		}
		ri := RouteItem{
			Device:  "lo",
			From:    net.IPv4(127, 0, 0, 1).To4(),
			NextHop: net.IPv4(127, 0, 0, 1).To4(),
		}
		ss := strings.Split(string(output), "\n")
		if len(ss) == 0 {
			return rt, nil
		}
		_, n, err := net.ParseCIDR(ss[0])
		if err != nil {
			return rt, nil
		}
		ri.To = *(n)
		rt = append(rt, ri)
		rt.Sort()
		return rt, nil
	}
	return nil, errors.New("need command ip or ifconfig and route")
}
